/* @flow */

import config from 'core/config';
import { warn, cached } from 'core/util/index';
import { mark, measure } from 'core/util/perf';

import Vue from './runtime/index';
import { query } from './util/index';
import { compileToFunctions } from './compiler/index';
import {
  shouldDecodeNewlines,
  shouldDecodeNewlinesForHref,
} from './util/compat';

const idToTemplate = cached((id) => {
  const el = query(id);
  return el && el.innerHTML;
});

// 先缓存了原型上的 $mount 方法
// 该方法定义在 src/platform/web/runtime/index.js
// 方便扩展原来默认的 $mount
const mount = Vue.prototype.$mount;

/**
 * 当我们使用 Runtime + Compiler 的 Vue.js，
 * 它的入口是 src/platforms/web/entry-runtime-with-compiler.js，
 * 看一下它对 $mount 函数的定义：
 * @param {*} el
 * @param {*} hydrating
 */
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el);

  /* istanbul ignore if */
  // 不允许挂载到 body 和 html 元素上
  /**
   * 对 el 做了判断，不允许挂载 到 body 和 html 根节点上
   */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' &&
      warn(
        `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
      );
    return this;
  }

  const options = this.$options;
  /**
   * template:`<App/>`
   * 如果没有定义 render 方法
   * 解析 template 和 el 元素到 render 函数，使用非 render 函数进行渲染
   * 在 vue2 中所有组件都需要渲染到 render 方法
   * 这是 vue 的一个在线编译的过程
   */
  if (!options.render) {
    let template = options.template;
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template);
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            );
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML;
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this);
        }
        return this;
      }
    } else if (el) {
      template = getOuterHTML(el);
    }

    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile');
      }

      /**
       * vue 提供的一个 '在线编译' 的效果，都会转换成 render 字段的形式
       */
      const { render, staticRenderFns } = compileToFunctions(
        template,
        {
          outputSourceRange: process.env.NODE_ENV !== 'production',
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments,
        },
        this
      );
      options.render = render;
      options.staticRenderFns = staticRenderFns;

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end');
        measure(`vue ${this._name} compile`, 'compile', 'compile end');
      }
    }
  }
  // 调用 mount 函数
  return mount.call(this, el, hydrating);
};

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML(el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML;
  } else {
    const container = document.createElement('div');
    container.appendChild(el.cloneNode(true));
    return container.innerHTML;
  }
}

Vue.compile = compileToFunctions;

export default Vue;
